/***************************************************************************
 *cr                                                                       
 *cr            (C) Copyright 1995-2019 The Board of Trustees of the           
 *cr                        University of Illinois                       
 *cr                         All Rights Reserved                        
 *cr                                                                   
 ***************************************************************************/

// generated by Fast Light User Interface Designer (fluid) version 1.0009

#include <stdio.h>
#include <math.h>
#include "GeometryFltkMenu.h"
#include "GeometryList.h"
#include "Command.h"
#include "VMDApp.h"
#include "PickList.h"
#include "Atom.h"
#include "Molecule.h"
#include "MoleculeList.h"
#include "FL/fl_draw.H"
#include "FL/forms.H"
#include "FL/Fl_Input.H"
#include "TextEvent.h"
#include "Inform.h"

#if defined(VMD_FLCHART_WORKAROUND)

/// A replacement for Fl_Chart, which has a bug that causes it to sometimes
/// draw outside its bounding box.
class myFl_Chart : public Fl_Widget {
protected:
  void draw();

private:
  int num;
  float *values;
  float min, max;
  int imin, imax;

public:
  myFl_Chart(int, int, int, int, const char * = 0);
  ~myFl_Chart();

  void clear();
  void set_data(const float *data, int n);
};

myFl_Chart::myFl_Chart(int x, int y, int w, int h, const char *l)
: Fl_Widget(x,y,w,h,l) {
  box(FL_BORDER_BOX);
  align(FL_ALIGN_BOTTOM);
  num = 0;
  values = NULL;
  min = max = 0;
  imin = imax = 0;
}

myFl_Chart::~myFl_Chart() {
  delete [] values;
}

void myFl_Chart::clear() {
  delete [] values;
  values = NULL;
  num = 0;
  redraw();
}

void myFl_Chart::set_data(const float *data, int n) {
  if (n < 1) {
    clear();
    return;
  }
  delete [] values;
  values = new float[n];
  memcpy(values, data, n*sizeof(float));
  num = n;
  min = max = data[0];
  imin = imax = 0;
  for (int i=1; i<n; i++) {
    if (min > data[i]) { min = data[i]; imin = i; }
    if (max < data[i]) { max = data[i]; imax = i; }
  }
  redraw();
}

// Use Fltk's definition of rint, since Windows sees fit to omit rint
// from its math library.  This definition isn't correct for negative
// arguments, but it's what's used in the original Fl_Chart class,
// from which this code was derived, so I'll keep doing it there way.
static double fltk_rint(double v) {return floor(v+.5);}

void myFl_Chart::draw() {
  int xx, yy, ww, hh;
  if (!num) return;
  xx = x()+9;
  yy = y()+9;
  ww = w()-2*9;
  hh = h()-2*9;

  draw_box();

  double lh = fl_height(); // compensate for text height?
  double incr;
  int zeroh;
  if (min > 0) {
    incr = (hh-2*lh)/max;
    zeroh = yy+hh-9;
  } else if (max < 0) {
    incr = (hh-2*lh)/min;
    zeroh = yy-9;
  } else {
    incr = (hh-2*lh)/(max-min);
    zeroh = yy+hh+(int)fltk_rint(min*incr) - 9;
  }
  double bwidth = ww/double(num);

  for (int i=1; i<num; i++) {
    int x0 = xx + (int)fltk_rint((i-.5)*bwidth);
    int x1 = xx + (int)fltk_rint((i+.5)*bwidth);
    int y0 = zeroh - (int)fltk_rint(values[i-1]*incr);
    int y1 = zeroh - (int)fltk_rint(values[i]*incr);
    int color = FL_GREEN;
    
    if (i == imin) color = FL_RED;
    else if (i == imax) color = FL_BLUE;
    fl_color(color);
    if ((values[i-1]>0.0)!=(values[i]>0.0)) {
      double ttt = values[i-1]/(values[i-1]-values[i]);
      int xt = xx + (int)fltk_rint((i-.5+ttt)*bwidth);
      fl_polygon(x0,zeroh, x0,y0, xt,zeroh);
      fl_polygon(xt,zeroh, x1,y1, x1,zeroh);
    } else {
      //assert(x0 >= xx && x0 <= xx+ww);
      //assert(x1 >= xx && x1 <= xx+ww);
      //assert(y0 >= yy && y0 <= yy+hh);
      //assert(y1 >= yy && y1 <= yy+hh);
      //assert(zeroh >= yy && zeroh <= yy+hh);
      fl_polygon(x0, zeroh, x0, y0, x1, y1, x1, zeroh);
    }
    fl_color(FL_BLACK);
    fl_line(x0,y0,x1,y1);
  }
  fl_line(xx, zeroh, xx+ww, zeroh);
  char buf[30];
  sprintf(buf, "%d: %3.2f", imin, min);
  fl_draw(buf, xx+(int)fltk_rint((imin+.5)*bwidth), zeroh-(int)fltk_rint(min*incr),0,0, 
       min >= 0 ? FL_ALIGN_BOTTOM : FL_ALIGN_TOP);
  sprintf(buf, "%d: %3.2f", imax, max);
  fl_draw(buf, xx+(int)fltk_rint((imax+.5)*bwidth), zeroh-(int)fltk_rint(max*incr),0,0, 
       max >= 0 ? FL_ALIGN_BOTTOM : FL_ALIGN_TOP);
}

#endif // VMD_FLCHART_WORKAROUND


void GeometryFltkMenu::handle_pick(Molecule *m, int atomid, float value) {
  char buf[64];
  
  if (!m || !m->current()) return;
  MolAtom *atm = m->atom(atomid);
  float *framepos = m->current()->pos;
  
  char *molnamebuf = new char[20+strlen(m->molname())];
  sprintf(molnamebuf, "%d: %s", m->id(), m->molname());
  pickedmolecule->value(molnamebuf);
  delete [] molnamebuf;
  pickedresname->value(m->resNames.name(atm->resnameindex));
  sprintf(buf, "%d", atm->resid);
  pickedresid->value(buf);
  pickedname->value(m->atomNames.name(atm->nameindex));
  pickedtype->value(m->atomTypes.name(atm->typeindex));
  sprintf(buf, "%d", atomid);
  pickedindex->value(buf);
  pickedchain->value(m->chainNames.name(atm->chainindex));
  pickedsegname->value(m->segNames.name(atm->segnameindex));
  sprintf(buf, "%8.3f   %8.3f   %8.3f", framepos[3*atomid+0], 
    framepos[3*atomid+1], framepos[3*atomid+2]);
  pickedpos->value(buf);
  sprintf(buf, "%8.3f", value);
  pickedvalue->value(buf);
}
 
void GeometryFltkMenu::fill_label_browser() {
  int cat = labeltypechooser->value();
  GeomListPtr geomlist = glist->geom_list(cat);
  int gnum = geomlist->num();

  int curnum = labelbrowser->size();
  // Only clear if the number has changed so that when we show or hide
  // it doesn't reset the items we've selected.
  if (curnum != gnum) {
    labelbrowser->clear();
  }
  // for each label, break up the name by '/'.  Each element goes in its
  // own column in the browser, with a format string preceding to specify
  // the color of the item.  
  for (int j=0; j<gnum; j++) {
    char geomname[128];
    char geomstr[128];
    char buf[20];
    GeometryMol *g = (*geomlist)[j];
    strcpy(geomname, g->name());
    geomstr[0] = '\0';
    char *start = geomname; 
    char *s = start;
    int done = 0;
    while (!done) {
      if (*s == '/' || *s == '\0') {
        if (!*s) 
          done = 1;   
        *s = '\0';
        // color 203 is "Orchid".
        sprintf(buf, "%s%s\t", g->displayed() ? VMDMENU_GEOM_ACTIVE : VMDMENU_GEOM_INACTIVE, start);
        strcat(geomstr, buf);
        start = s+1;
        s = start;
      } else {
        s++;
      }
    }
    if (curnum != gnum)
      labelbrowser->add(geomstr);
    else
      labelbrowser->text(j+1, geomstr);
  }
}

void GeometryFltkMenu::update_labelprops() {
  char tmpbuf[100];
  float textsize = app->label_get_text_size();
  sprintf(tmpbuf, "%5.2f", textsize);
  textsizeinput->value(tmpbuf);
  textsizeslider->value(textsize);

  float textthickness = app->label_get_text_thickness();
  sprintf(tmpbuf, "%5.2f", textthickness);
  textthicknessinput->value(tmpbuf);
  textthicknessslider->value(textthickness);

  // get the offset of the selected label, if any
  int ind = labelbrowser->value();
  if (ind > 0) {
    textoffsetpositioner->activate();
    offsetresetbutton->activate();
    const float *offset = app->geometryList->getTextOffset(
        labeltypechooser->text(), ind-1);
    if (offset) {
      textoffsetpositioner->xvalue(offset[0]);
      textoffsetpositioner->yvalue(-offset[1]);
    } else {
      msgWarn << "No label found to update text offset!" << sendmsg;
    }
    if (labeltypechooser->value() == 0) { // only for atom labels
      textformatinput->activate();
      if (!user_is_typing_in_format_input) {
        const char *format = app->geometryList->getTextFormat(
          labeltypechooser->text(), ind-1);
        if (format) {
          textformatinput->value(format);
        } else {
          msgWarn << "No label found to update text format!" << sendmsg;
        }
      }
    }
  } else {
    textoffsetpositioner->deactivate();
    offsetresetbutton->deactivate();
  }
  if (ind < 1 || labeltypechooser->value() != 0) {
    textformatinput->deactivate();
  }
}

// populate the geometry type chooser.
void GeometryFltkMenu::update_geometry_types() {
  labeltypechooser->clear();
  for (int j=0; j<glist->num_lists(); j++)
    labeltypechooser->add(glist->geom_list_name(j));
  labeltypechooser->value(0);
}
 
void GeometryFltkMenu::typechooser_cb(Fl_Widget *, void *v) {
  ((GeometryFltkMenu *)v)->fill_label_browser();
  ((GeometryFltkMenu *)v)->update_labelprops();
}

void GeometryFltkMenu::graphinwindow_cb(Fl_Widget *, void *v) {
  GeometryFltkMenu *self = (GeometryFltkMenu *)v;
  int last_clicked = self->labelbrowser->value();
  if (!self->labelbrowser->selected(last_clicked)) {
    return;
  }

  int item = last_clicked-1;
  GeomListPtr geomlist = self->glist->geom_list(self->labeltypechooser->value());
  // make sure the label hasn't been deleted out from under us
  if (item >= geomlist->num()) {
    return;
  }
  GeometryMol *geom = (*geomlist)[item];
  ResizeArray<float> values;
  if (!geom->calculate_all(values)) {
    return;
  }
  if (values.num() < 1) {
    return;
  }
#if defined(VMD_FLCHART_WORKAROUND)
  // XXX workaround Fl_Chart bug that could cause out-of-bounds drawing
  self->chart->set_data(&(values[0]), values.num());
#else
  int i;
  self->chart->clear();
  if (values.num() > 0) {
    self->chart->maxsize(values.num());
    for (i=0; i<values.num(); i++) {
      self->chart->insert(i, values[i], NULL, 0);
    }
  }
#endif
}

void GeometryFltkMenu::show_cb(Fl_Widget *, void *v) {
  GeometryFltkMenu *self = (GeometryFltkMenu *)v;
  for (int i=self->labelbrowser->size(); i>0; i--) {
    if (self->labelbrowser->selected(i)) {
      self->app->label_show(self->labeltypechooser->text(), i-1, 1);
    }
  }
}

void GeometryFltkMenu::hide_cb(Fl_Widget *, void *v) {
  GeometryFltkMenu *self = (GeometryFltkMenu *)v;
  for (int i=self->labelbrowser->size(); i>0; i--) {
    if (self->labelbrowser->selected(i)) {
      self->app->label_show(self->labeltypechooser->text(), i-1, 0);
    }
  }
}

void GeometryFltkMenu::delete_cb(Fl_Widget *, void *v) {
  GeometryFltkMenu *self = (GeometryFltkMenu *)v;
  ResizeArray<int> tmp;
  int i;
  for (i=self->labelbrowser->size(); i>0; i--) {
    if (self->labelbrowser->selected(i)) tmp.append(i-1);
  }
  for (i=0; i<tmp.num(); i++) 
    self->app->label_delete(self->labeltypechooser->text(), tmp[i]);
}

void GeometryFltkMenu::labelbrowser_cb(Fl_Widget *, void *v) {
  GeometryFltkMenu *self = (GeometryFltkMenu *)v;
  int ind = self->labelbrowser->value();
  if (ind < 1) return;
  int labeltype = self->labeltypechooser->value();
  //GeomListPtr geomlist = self->glist->geom_list(labeltype);
  GeometryMol *geom = (*(self->glist->geom_list(labeltype)))[ind-1];
  int atomid = geom->com_index(0);
  int molid = geom->obj_index(0);
  Molecule *m = self->app->moleculeList->mol_from_id(molid);
  // make sure we have the up-to-date value.
  geom->calculate();
  self->handle_pick(m, atomid, geom->value());
  if (self->previewcheckbutton->value()) {
    self->graphinwindow_cb(self->chart, self);
  }
  // show the correct label offset
  self->update_labelprops();
}

void GeometryFltkMenu::exportgraph_cb(Fl_Widget *, void *v) {
  GeometryFltkMenu *self = (GeometryFltkMenu *)v;
  if (self->labelbrowser->size() < 1) {
    msgInfo << "No labels selected" << sendmsg;
    return;
  }
  GeomListPtr geomlist = self->glist->geom_list(self->labeltypechooser->value());
  if (geomlist->num() < 1) {
    msgErr << "GeometryFltkMenu::exportgraph_cb: No labels in list!  That's weird." << sendmsg;
    return;
  }
  ResizeArray<int> items;
  for (int item=0; item <self->labelbrowser->size(); item++) {
    if (!self->labelbrowser->selected(1+item)) continue;
    items.append(item);
    GeometryMol *geom = (*geomlist)[item];
    ResizeArray<float> values;
    if (!geom->calculate_all(values)) {
      msgErr << "GeometryFltkMenu::exportgraph_cb: Couldn't calculate values" << sendmsg;
      return;
    }
    if (values.num() < 1) {
      msgErr << "Nothing to plot!" << sendmsg;
      return;
    }
  }
  self->runcommand(new GraphLabelEvent(self->labeltypechooser->text(),
        &items[0], items.num()));
}

// XXX Massive replication of code in exportgraph_cb, savetofile_cb, and
// graphinwindow_cb.
void GeometryFltkMenu::savetofile_cb(Fl_Widget *, void *v) {
  GeometryFltkMenu *self = (GeometryFltkMenu *)v;
  if (self->labelbrowser->size() < 1) {
    msgInfo << "No labels selected" << sendmsg;
    return;
  }
  GeomListPtr geomlist = self->glist->geom_list(self->labeltypechooser->value());
  if (geomlist->num() < 1) {
    msgErr << "GeometryFltkMenu::savetofile_cb: No labels in list!  That's weird." << sendmsg;
    return;
  }
  for (int item=0; item <self->labelbrowser->size(); item++) {
    if (!self->labelbrowser->selected(1+item)) continue;
    GeometryMol *geom = (*geomlist)[item];
    ResizeArray<float> values;
    if (!geom->calculate_all(values)) {
      msgErr << "GeometryFltkMenu::savetofile_cb: Couldn't calculate values" << sendmsg;
      return;
    }
    if (values.num() < 1) {
      msgErr << "Nothing to plot!" << sendmsg;
      return;
    }
    char *file = self->app->vmd_choose_file(geom->unique_name(),
                                            "*.dat",
                                            "data file",
                                            1);
    if (!file) continue;
    FILE *fd = fopen(file, "w");
    for (int i=0; i<values.num(); i++) {
      fprintf(fd, "%d\t%f\n", i, values[i]);
    }
    fclose(fd);
  }
}

// handlers for adjusting label offset
static void textoffset_cb(Fl_Widget *w, void *v) {
  Fl_Positioner *p = (Fl_Positioner *)w;

  float x = (float)  p->xvalue();
  float y = (float) -p->yvalue();
  ((GeometryFltkMenu *)v)->apply_offset_to_selected_labels(x, y);
}

static void offsetreset_cb(Fl_Widget *, void *v) {
  ((GeometryFltkMenu *)v)->apply_offset_to_selected_labels(0, 0);
}

void GeometryFltkMenu::apply_offset_to_selected_labels(float x, float y) {
  // get the selected geometry category
  const char *geomtype = labeltypechooser->text();

  // get all the currently selected labels
  for (int l=labelbrowser->size(); l>0; l--) {
    if (labelbrowser->selected(l)) {
      app->label_set_textoffset(geomtype, l-1, x, y);
    }
  }
}

// handlers for setting label format
static void textformat_cb(Fl_Widget *w, void *v) {
  Fl_Input *input = (Fl_Input *)w;
  ((GeometryFltkMenu *)v)->apply_format_to_selected_labels(input->value());
}
void GeometryFltkMenu::apply_format_to_selected_labels(const char *format) {
  // get the selected geometry category
  const char *geomtype = labeltypechooser->text();

  // prevent the format entry from getting updated as we type, because
  // it selects all text, which makes it impossible to type more the one
  // character at a time.
  user_is_typing_in_format_input = TRUE;
  

  // get all the currently selected labels
  for (int l=labelbrowser->size(); l>0; l--) {
    if (labelbrowser->selected(l)) {
      app->label_set_textformat(geomtype, l-1, format);
    }
  }
  user_is_typing_in_format_input = FALSE;
}

static void textsizeslidercb(Fl_Widget *w, void *v) {
  Fl_Slider *slider = (Fl_Slider *)w;
  VMDApp *app = (VMDApp *)v;
  app->label_set_text_size((float) slider->value());
}

static void textsizeinputcb(Fl_Widget *w, void *v) {
  Fl_Float_Input *input = (Fl_Float_Input *)w;
  VMDApp *app = (VMDApp *)v;
  char *endptr = NULL;
  const char *strval = input->value();
  double val = strtod(strval, &endptr);
  if (endptr != strval) {
    // valid conversion performed
    app->label_set_text_size((float)val);
  }
}

static void textthicknessslidercb(Fl_Widget *w, void *v) {
  Fl_Slider *slider = (Fl_Slider *)w;
  VMDApp *app = (VMDApp *)v;
  app->label_set_text_thickness((float) slider->value());
}

static void textthicknessinputcb(Fl_Widget *w, void *v) {
  Fl_Float_Input *input = (Fl_Float_Input *)w;
  VMDApp *app = (VMDApp *)v;
  char *endptr = NULL;
  const char *strval = input->value();
  double val = strtod(strval, &endptr);
  if (endptr != strval) {
    // valid conversion performed
    app->label_set_text_thickness((float)val);
  }
}

class My_Fl_Positioner : public Fl_Positioner {
  public:
    My_Fl_Positioner(int x, int y, int w, int h, const char *t=NULL)
    : Fl_Positioner(x, y, w, h, t) {}
    virtual void draw() {
      Fl_Positioner::draw();
      fl_circle(90+50, 190+50, 5);
    }
};

static const int columns[] = {85, 85, 85, 85, 0};
void GeometryFltkMenu::make_window() {
  size(380,335);

  {
    labelbrowser = new Fl_Multi_Browser(10, 45, 360, 70);
    VMDFLTKTOOLTIP(labelbrowser, "Select label to modify")
    labelbrowser->color(VMDMENU_BROWSER_BG);
    labelbrowser->selection_color(VMDMENU_BROWSER_SEL);
    labelbrowser->callback(labelbrowser_cb, this);
    labelbrowser->column_widths(columns);
                
    { Fl_Choice* o = labeltypechooser = new Fl_Choice(10, 15, 95, 25);
      VMDFLTKTOOLTIP(labeltypechooser, "Select label type to show in browser")
      o->color(VMDMENU_CHOOSER_BG);
      o->selection_color(VMDMENU_CHOOSER_SEL);
      o->box(FL_THIN_UP_BOX);
      o->labeltype(FL_NO_LABEL);
      o->callback(typechooser_cb, this);
    }
    showbutton = new Fl_Button(125, 15, 75, 25, "Show");
#if defined(VMDMENU_WINDOW)
    showbutton->color(VMDMENU_WINDOW, FL_GRAY);
#endif
    VMDFLTKTOOLTIP(showbutton, "Show (unhide) selected label")
    showbutton->callback(show_cb, this);

    hidebutton = new Fl_Button(200, 15, 75, 25, "Hide");
#if defined(VMDMENU_WINDOW)
    hidebutton->color(VMDMENU_WINDOW, FL_GRAY);
#endif
    VMDFLTKTOOLTIP(hidebutton, "Hide (do not display) selected label")
    hidebutton->callback(hide_cb, this);

    deletebutton = new Fl_Button(275, 15, 75, 25, "Delete");
#if defined(VMDMENU_WINDOW)
    deletebutton->color(VMDMENU_WINDOW, FL_GRAY);
#endif
    VMDFLTKTOOLTIP(deletebutton, "Delete selected label")
    deletebutton->callback(delete_cb, this);

    { Fl_Tabs* o = new Fl_Tabs(10, 120, 360, 215);
#if defined(VMDMENU_WINDOW)
        o->color(VMDMENU_WINDOW, FL_GRAY);
        o->selection_color(VMDMENU_WINDOW);
#endif

      { Fl_Group* o = pickinggroup = new Fl_Group(35, 145, 315, 180, "Picked Atom");
#if defined(VMDMENU_WINDOW)
        o->color(VMDMENU_WINDOW, FL_GRAY);
        o->selection_color(VMDMENU_WINDOW);
#endif

        Fl_Widget *w;
        w = pickedmolecule = new Fl_Output(110, 155, 230, 25, "Molecule:");
        VMDFLTKTOOLTIP(pickedmolecule, "Parent molecule of picked atom")
        w->selection_color(VMDMENU_VALUE_SEL);

        w = pickedresname = new Fl_Output(110, 205, 70, 25, "ResName:");
        VMDFLTKTOOLTIP(pickedresname, "Parent residue name of picked atom")
        w->selection_color(VMDMENU_VALUE_SEL);

        w = pickedresid = new Fl_Output(110, 230, 70, 25, "ResID:");
        VMDFLTKTOOLTIP(pickedresid, "Parent residue ID of picked atom")
        w->selection_color(VMDMENU_VALUE_SEL);

        w = pickedname = new Fl_Output(110, 255, 70, 25, "Name:");
        VMDFLTKTOOLTIP(pickedname, "Name of picked atom")
        w->selection_color(VMDMENU_VALUE_SEL);

        w = pickedtype = new Fl_Output(110, 280, 70, 25, "Type:");
        VMDFLTKTOOLTIP(pickedtype, "Type of picked atom")
        w->selection_color(VMDMENU_VALUE_SEL);

        w = pickedpos = new Fl_Output(110, 180, 230, 25, "XYZ:");
        VMDFLTKTOOLTIP(pickedpos, "Coordinates of picked atom")
        w->selection_color(VMDMENU_VALUE_SEL);

        w = pickedchain = new Fl_Output(270, 205, 70, 25, "Chain:");
        VMDFLTKTOOLTIP(pickedchain, "Parent chain of picked atom")
        w->selection_color(VMDMENU_VALUE_SEL);

        w = pickedsegname = new Fl_Output(270, 230, 70, 25, "SegName:");
        VMDFLTKTOOLTIP(pickedsegname, "Parent segment name of picked atom")
        w->selection_color(VMDMENU_VALUE_SEL);

        w = pickedindex = new Fl_Output(270, 255, 70, 25, "Index:");
        VMDFLTKTOOLTIP(pickedindex, "Index of picked atom")
        w->selection_color(VMDMENU_VALUE_SEL);

        w = pickedvalue = new Fl_Output(270, 280, 70, 25, "Value:");
        VMDFLTKTOOLTIP(pickedvalue, "Value of label (bond length, angle, dihedral, etc)")
        w->selection_color(VMDMENU_VALUE_SEL);

        o->end();
      }
      { Fl_Group* o = geometrygroup = new Fl_Group(35, 145, 315, 180, "Graph");
#if defined(VMDMENU_WINDOW)
        o->color(VMDMENU_WINDOW, FL_GRAY);
        o->selection_color(VMDMENU_WINDOW);
#endif
        o->hide();
        savetofilebutton = new Fl_Button(285, 155, 55, 25, "Save...");
#if defined(VMDMENU_WINDOW)
        savetofilebutton->color(VMDMENU_WINDOW, FL_GRAY);
#endif
        VMDFLTKTOOLTIP(savetofilebutton, "Save label values to a file")
        savetofilebutton->callback(savetofile_cb, this);

        exportgraphbutton = new Fl_Button(200, 155, 65, 25, "Graph...");
#if defined(VMDMENU_WINDOW)
        exportgraphbutton->color(VMDMENU_WINDOW, FL_GRAY);
#endif
        VMDFLTKTOOLTIP(exportgraphbutton, "Export label values to other graphing tools")
        exportgraphbutton->callback(exportgraph_cb, this);

        previewcheckbutton = new Fl_Check_Button(40, 155, 100, 25, "Show preview");
        VMDFLTKTOOLTIP(previewcheckbutton, "Show quick preview graph of label value")
#if defined(VMD_FLCHART_WORKAROUND)
        // XXX workaround Fl_Chart bug that could cause out-of-bounds drawing
        chart = new myFl_Chart(45, 190, 295, 125);
#else
        //chart = new Fl_Chart(20, 210, 330, 90);
        chart = new Fl_Chart(45, 190, 295, 125);
        chart->type(FL_FILLED_CHART);
#endif
        o->end();
      }
      { Fl_Group *o = propertiesgroup = new Fl_Group(35, 145, 315, 180, "Properties");
#if defined(VMDMENU_WINDOW)
        o->color(VMDMENU_WINDOW, FL_GRAY);
        o->selection_color(VMDMENU_WINDOW);
#endif
        o->hide();
        textoffsetpositioner = new My_Fl_Positioner(90, 155, 100, 100, "Offset:");
        textoffsetpositioner->align(FL_ALIGN_LEFT);
        textoffsetpositioner->color(VMDMENU_POSITIONER_BG);
        textoffsetpositioner->selection_color(VMDMENU_POSITIONER_SEL);
        textoffsetpositioner->when(FL_WHEN_CHANGED);
        textoffsetpositioner->callback(textoffset_cb, this);
        // make more room on the left side and below so it's possible to get atom labels
        // all the way to the left of their atom
        textoffsetpositioner->xbounds(-.7, .7);
        textoffsetpositioner->ybounds(-.5, .5);
        textoffsetpositioner->value(0,0);
        VMDFLTKTOOLTIP(textoffsetpositioner, "Set the label offset relative to its default position");

        offsetresetbutton = new Fl_Button(200, 193, 55, 25, "Reset");
#if defined(VMDMENU_WINDOW)
        offsetresetbutton->color(VMDMENU_WINDOW, FL_GRAY);
#endif
        VMDFLTKTOOLTIP(offsetresetbutton, "Reset the label offset to (0,0)");
        offsetresetbutton->callback(offsetreset_cb, this);

        textformatinput = new Fl_Input(90, 260, 245, 25, "Format:");
        textformatinput->selection_color(VMDMENU_VALUE_SEL);
        textformatinput->when(FL_WHEN_CHANGED);
        textformatinput->callback(textformat_cb, this);
        VMDFLTKTOOLTIP(textformatinput, "Set selected atom labels with substitutions: %R->resname, %1R->1-char resname, %r->camelcase resname, %d->resid, %a->name, %q->charge, %i->0-index, %1i->1-index, %p->atomic number, %e->element, %s->segname, %o->occupancy, %b->beta, %c->chain");

        o->end();
      }
      { Fl_Group *o = globalpropsgroup = new Fl_Group(35, 145, 315, 180, "Global Properties");
#if defined(VMDMENU_WINDOW)
        o->color(VMDMENU_WINDOW, FL_GRAY);
        o->selection_color(VMDMENU_WINDOW);
#endif
        o->hide();
        textsizeinput = new Fl_Float_Input(130, 155, 50, 25, "Text Size:");
        VMDFLTKTOOLTIP(textsizeinput, "Set label text size")
        textsizeinput->when(FL_WHEN_ENTER_KEY);
        textsizeinput->selection_color(VMDMENU_VALUE_SEL);
        textsizeinput->callback(textsizeinputcb, app);

        textsizeslider = new Fl_Slider(180, 155, 180, 25);
        VMDFLTKTOOLTIP(textsizeslider, "Set label text size")
        textsizeslider->type(FL_HOR_SLIDER);
        textsizeslider->color(VMDMENU_SLIDER_BG);
        textsizeslider->when(FL_WHEN_CHANGED);
        textsizeslider->callback(textsizeslidercb, app); 
        textsizeslider->range(0, 5);

        textthicknessinput = new Fl_Float_Input(130, 190, 50, 25, "Text Thickness:");
        VMDFLTKTOOLTIP(textsizeinput, "Set label text line thickness")
        textthicknessinput->when(FL_WHEN_ENTER_KEY);
        textthicknessinput->selection_color(VMDMENU_VALUE_SEL);
        textthicknessinput->callback(textthicknessinputcb, app);

        textthicknessslider = new Fl_Slider(180, 190, 180, 25);
        VMDFLTKTOOLTIP(textsizeslider, "Set label text line thickness")
        textthicknessslider->type(FL_HOR_SLIDER);
        textthicknessslider->color(VMDMENU_SLIDER_BG);
        textthicknessslider->when(FL_WHEN_CHANGED);
        textthicknessslider->callback(textthicknessslidercb, app); 
        textthicknessslider->range(0, 5);

        o->end();
      }
      o->end();
    }
    Fl_Window::end();

    update_labelprops();
  }
}

GeometryFltkMenu::GeometryFltkMenu(VMDApp *vmdapp) 
: VMDFltkMenu("labels", "Labels", vmdapp), glist(vmdapp->geometryList) {

  command_wanted(Command::LABEL_ADD);
  command_wanted(Command::LABEL_DELETE);
  command_wanted(Command::LABEL_SHOW);
  command_wanted(Command::LABEL_ADDSPRING);
  command_wanted(Command::LABEL_TEXTSIZE);
  command_wanted(Command::LABEL_TEXTTHICKNESS);
  command_wanted(Command::LABEL_TEXTOFFSET);
  command_wanted(Command::LABEL_TEXTFORMAT);
  command_wanted(Command::ANIM_NEW_FRAME);
  command_wanted(Command::PICK_EVENT);
  command_wanted(Command::MOL_DEL);

  user_is_typing_in_format_input = FALSE;

  make_window();
  update_geometry_types();
}

int GeometryFltkMenu::act_on_command(int type, Command *cmd) {
  switch (type) {
  case Command::PICK_EVENT:
    {
      PickEvent *event = (PickEvent *)cmd;
      Molecule *m = app->moleculeList->check_pickable(event->pickable);
      if (m) 
        handle_pick(m, event->tag, 0);
    }
    break;

  case Command::LABEL_ADD:
  case Command::LABEL_ADDSPRING:
  case Command::LABEL_DELETE:
  case Command::LABEL_SHOW:
    fill_label_browser();
    update_labelprops();
    break;

  case Command::ANIM_NEW_FRAME:
    labelbrowser->do_callback();
    break;

  case Command::LABEL_TEXTSIZE:
  case Command::LABEL_TEXTTHICKNESS:
  case Command::LABEL_TEXTOFFSET:
  case Command::LABEL_TEXTFORMAT:
    update_labelprops();
    break;

  case Command::MOL_DEL:
    glist->prepare();     // mark all non-ok geoms and delete them before
    fill_label_browser(); // refilling the label browser with the current list
    update_labelprops();
    break;
  }
  return 1;
}

